import os
from typing_extensions import Annotated

from fastapi import (
    Depends, Form, HTTPException, Path, Query, Request, Response
)
from sqlalchemy import exists, inspect, select
from sqlalchemy.exc import ProgrammingError

from core.database import DBConnect
from core.exception import AlertException, TemplateDisabledException
from core.models import Auth, Board, GroupMember, Member
from core.settings import ENV_PATH, settings
from core.template import get_theme_list
from lib.captcha import get_current_captcha_cls
from lib.common import get_client_ip, get_current_admin_menu_id
from lib.dependency.auth import get_login_member_optional
from lib.member import get_admin_type
from lib.token import check_token
from service.current_connect_service import CurrentConnectService
from service.menu_service import MenuService
from service.poll_service import PollService
from service.popular_service import PopularService


async def get_variety_bo_table(
    bo_table_path: Annotated[str, Path(alias="bo_table", title="公告板表名", description="公告板表名")] = None,
    bo_table_form: Annotated[str, Form(alias="bo_table")] = None
):
    """
    按请求参数的类型接收公告板表名, 只返回一个表名
    - 按函数参数的顺序具有优先级
    """
    return bo_table_path or bo_table_form


async def get_variety_tokens(
    token_form: Annotated[str, Form(alias="token")] = None,
    token_query: Annotated[str, Query(alias="token")] = None
):
    """
    按请求参数的类型接收令牌, 只有一个令牌返回
    - 按函数参数的顺序具有优先级
    """
    return token_form or token_query


async def get_variety_theme(
    theme_form: Annotated[str, Form(alias="theme")] = None,
    theme_path: Annotated[str, Path(alias="theme")] = None
):
    """
    按请求参数类型接收变量, 只有一个变量返回
    - 按函数参数的顺序具有优先级
    """
    return theme_form or theme_path


async def validate_token(
    request: Request,
    token: Annotated[str, Depends(get_variety_tokens)]
):
    """Totin验证"""
    if not check_token(request, token):
        raise AlertException("令牌无效", 403)


async def validate_captcha(
    request: Request,
    response: Annotated[str, Form(alias="g-recaptcha-response")] = None
):
    """谷歌 reCAPTCHA 验证"""
    config = request.state.config
    captcha_cls = get_current_captcha_cls(config)

    if captcha_cls:
        captcha = captcha_cls(config)
        if captcha and (not await captcha.verify(response)):
            raise AlertException("captas不正确.", 400)


async def validate_super_admin(request: Request):
    """检查最高管理者是否"""
    if not request.state.is_super_admin:
        raise AlertException("只有高层管理人员可以访问.", 403)


async def validate_install():
    """安装检查"""

    db_connect = DBConnect()
    engine = db_connect.engine
    prefix = db_connect.table_prefix

    if (os.path.exists(ENV_PATH)
            and inspect(engine).has_table(prefix + "config")):
        raise AlertException(
            "安装已完成.\\n要重新安装 .env删除文件并重试.", 400, "/")


async def validate_theme(
        theme: Annotated[str, Depends(get_variety_theme)]):
    """验证主题"""
    theme = theme.strip()
    theme_list = get_theme_list()

    if theme not in theme_list:
        raise AlertException("您选择的主题未安装.", 404)

    return theme


async def check_group_access(
        request: Request,
        bo_table: Annotated[str, Path(...)]):
    """组访问权限检查"""

    with DBConnect().sessionLocal() as db:
        board = db.get(Board, bo_table)
        if not board:
            raise AlertException(f"{bo_table} : 这是一个不存在的公告板.", 404)

        group = board.group
        member = request.state.login_member

        # 组访问仅在使用时检查
        if group.gr_use_access:
            if not member:
                raise AlertException(
                    f"非成员无权访问此公告.\
                    \\n\\n如果您是会员，请登录并尝试.", 403)

            # 高层管理人员或组经理不检查访问权限
            if not get_admin_type(request, member.mb_id, group=group):
                exists_group_member = db.scalar(
                    exists(GroupMember)
                    .where(GroupMember.gr_id == group.gr_id, GroupMember.mb_id == member.mb_id).select()
                )
                if not exists_group_member:
                    raise AlertException(
                        f"`{board.bo_subject}` 您没有访问公告板的权限.\
                        \\n\\n如有任何问题，请联系经理.", 403)


async def check_admin_access(request: Request):
    """Manager页面访问权限检查"""

    with DBConnect().sessionLocal() as db:
        path = request.url.path
        ss_mb_id = request.session.get("ss_mb_id", "")

        if not ss_mb_id:
            raise AlertException("我需要登录.", 302, url="/bbs/login?url=" + path)
        elif not request.state.is_super_admin:
            method = request.method
            admin_menu_id = get_current_admin_menu_id(request)

            # 管理员菜单的权限检查
            if admin_menu_id:
                au_auth = db.scalar(
                    select(Auth.au_auth)
                    .where(Auth.mb_id == ss_mb_id, Auth.au_menu == admin_menu_id)
                ) or ""
                # 每个请求的权限检查
                # delete 请求是 GET 当它作为请求处理时，, 根据要求 "delete"检查以包括
                if ("delete" in path and not "d" in au_auth):
                    raise AlertException("您没有删除的权限.", 302, url="/")
                elif (method == "POST" and not "w" in au_auth):
                    raise AlertException("无更正权.", 302, url="/")
                elif (method == "GET" and not "r" in au_auth):
                    raise AlertException("您没有阅读的权限.", 302, url="/")
            # Manager主菜单ID因为没有, 检查您是否有其他菜单的权限
            else:
                exists_auth = db.scalar(
                    exists(Auth)
                    .where(Auth.mb_id == ss_mb_id).select()
                )
                if not exists_auth:
                    raise AlertException(
                        "仅可访问具有管理权限的高层管理人员或成员.", 302, url="/")


async def common_search_query_params(
        sst: str = Query(default="", title="对齐字段", description="对齐字段"),
        sod: str = Query(default="and", title="按 ------------------------------------------------------------------------------ 搜索", description="검색연산자", pattern="and|or"),
        sod: str = Query(default="and", title="按 ------------------------------------------------------------------------------ 搜索", description="검색연산자", pattern="and|or"),
        sfl: str = Query(default="wr_subject||wr_content", title="搜索字段", description="搜索字段"),
        stx: str = Query(default="", title="搜索词", description="搜索词"),
        sca: str = Query(default="", title="分类", description="搜索分类"),
        current_page: str = Query(default="1", alias="page")):
    """常用的 Query String 接收参数的函数"""
    try:
        current_page = int(current_page)
    except ValueError:
        # current_page如果无法转换为整数，则为默认值 1设置为使用
        current_page = 1
    return {"sst": sst, "sod": sod, "sfl": sfl, "stx": stx, "sca": sca, "current_page": current_page}


async def check_use_template():
    """检查是否使用模板"""
    if not settings.USE_TEMPLATE:
        raise TemplateDisabledException(detail="模板不可用.")


async def check_use_api():
    """API 使用检验"""
    if not settings.USE_API:
        raise HTTPException(status_code=404, detail="API 这是不可能使用.")


async def no_cache_response(response: Response):
    """设置缓存控制标头的依赖函数"""
    response.headers["Cache-Control"] = "no-store"
    response.headers["Pragma"] = "no-cache"
    response.headers["Expires"] = "0"


async def set_template_basic_data(
    request: Request,
    current_connect_service: Annotated[CurrentConnectService, Depends()],
    menu_service: Annotated[MenuService, Depends()],
    poll_service: Annotated[PollService, Depends()],
    popular_service: Annotated[PopularService, Depends()]
):
    """设置模板默认查询数据"""
    template_data = {
        "current_login_count": current_connect_service.fetch_total_records(),
        "menus": menu_service.fetch_menus(),
        "poll": poll_service.fetch_latest_poll(),
        "populars": popular_service.fetch_populars(),
    }
    request.state.template_data = template_data


async def set_current_connect(
        request: Request,
        service: Annotated[CurrentConnectService, Depends()],
        member: Annotated[Member, Depends(get_login_member_optional)]):
    """设置当前访问信息"""
    try:
        current_ip = get_client_ip(request)
        path = request.url.path
        mb_id = getattr(member, "mb_id", "")

        if not request.state.is_super_admin:
            current_login = service.fetch_current_connect(current_ip)
            if current_login:
                service.update_current_connect(current_login, path, mb_id)
            else:
                service.create_current_connect(current_ip, path, mb_id)

        # 删除当前登录历史记录
        service.delete_current_connect()

    except ProgrammingError as e:
        print(e)


def validate_login_url(request: Request, url: str = Form(default="/")):
    """
    登录后移动 URL 验证
    - URL这不是绝对路径或, 如果域不同 "/"罗利直接
    验证登录后要移动的URL
-如果URL不是绝对路径，或者域不同，则直接指向“/”
    """
    domain = request.url.hostname

    if domain is None:
        return "/"

    if (url.startswith("//")
            or (not url.startswith("/") and domain not in url)):
        return "/"

    return url
